<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GeoHash地图可视化工具</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <script src="https://webapi.amap.com/maps?v=2.0&key=dd6bbfd545d0ccad0544c837617da265"></script>
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#165DFF',
                        secondary: '#0FC6C2',
                        accent: '#722ED1',
                        success: '#00B42A',
                        warning: '#FF7D00',
                        danger: '#F53F3F',
                        dark: '#1D2129',
                        light: '#F2F3F5'
                    },
                    fontFamily: {
                        inter: ['Inter', 'system-ui', 'sans-serif'],
                    },
                }
            }
        }
    </script>
    <style type="text/tailwindcss">
        @layer utilities {
            .content-auto {
                content-visibility: auto;
            }
            .map-container {
                height: calc(100vh - 160px);
            }
            .geohash-grid {
                stroke: rgba(22, 93, 255, 0.7);
                stroke-width: 1;
                fill: rgba(22, 93, 255, 0.1);
            }
            .geohash-grid-hover {
                stroke: rgba(114, 46, 209, 0.9);
                stroke-width: 2;
                fill: rgba(114, 46, 209, 0.2);
            }
        }
    </style>
</head>
<body class="font-inter bg-gray-50 text-dark">
    <header class="bg-white shadow-sm">
        <div class="container mx-auto px-4 py-4 flex justify-between items-center">
            <div class="flex items-center space-x-2">
                <i class="fa fa-map-marker text-primary text-2xl"></i>
                <h1 class="text-xl font-bold text-primary">GeoHash地图可视化工具</h1>
            </div>
            <div class="flex items-center space-x-4">
                <button id="helpBtn" class="text-gray-600 hover:text-primary transition-colors">
                    <i class="fa fa-question-circle mr-1"></i>帮助
                </button>
                <button id="aboutBtn" class="text-gray-600 hover:text-primary transition-colors">
                    <i class="fa fa-info-circle mr-1"></i>关于
                </button>
            </div>
        </div>
    </header>

    <main class="container mx-auto px-4 py-6">
        <div class="bg-white rounded-lg shadow-sm p-4 mb-6">
            <div class="flex flex-col md:flex-row gap-4">
                <div class="flex-1">
                    <label for="geohashInput" class="block text-sm font-medium text-gray-700 mb-1">输入GeoHash（每行一个）</label>
                    <textarea id="geohashInput" rows="4" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all" placeholder="例如：wx4g0s,wx4g0u"></textarea>
                </div>
                <div class="flex flex-col justify-between">
                    <div>
                        <label for="precisionSelect" class="block text-sm font-medium text-gray-700 mb-1">网格精度</label>
                        <select id="precisionSelect" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all">
                            <option value="5">5位（约5km×5km）</option>
                            <option value="6" selected>6位（约1.2km×0.6km）</option>
                            <option value="7">7位（约150m×150m）</option>
                            <option value="8">8位（约38m×19m）</option>
                            <option value="9">9位（约4.8m×4.8m）</option>
                        </select>
                    </div>
                    <div class="flex space-x-2">
                        <button id="clearBtn" class="flex-1 px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-md transition-colors">
                            <i class="fa fa-eraser mr-1"></i>清空
                        </button>
                        <button id="drawBtn" class="flex-1 px-4 py-2 bg-primary hover:bg-primary/90 text-white rounded-md transition-colors">
                            <i class="fa fa-map mr-1"></i>绘制
                        </button>
                    </div>
                </div>
            </div>
            <div class="mt-4 flex flex-wrap gap-2">
                <div class="flex items-center">
                    <span class="inline-block w-4 h-4 bg-primary/10 border border-primary/70 rounded-sm"></span>
                    <span class="ml-1 text-sm text-gray-600">GeoHash网格</span>
                </div>
                <div class="flex items-center">
                    <span class="inline-block w-4 h-4 bg-accent/20 border border-accent/90 rounded-sm"></span>
                    <span class="ml-1 text-sm text-gray-600">当前选中</span>
                </div>
                <div class="flex items-center">
                    <span class="inline-block w-4 h-4 bg-success/10 border border-success/70 rounded-sm"></span>
                    <span class="ml-1 text-sm text-gray-600">中心点</span>
                </div>
            </div>
        </div>

        <div class="bg-white rounded-lg shadow-sm overflow-hidden">
            <div id="mapContainer" class="map-container w-full"></div>
        </div>

        <div id="infoPanel" class="mt-4 bg-white rounded-lg shadow-sm p-4 hidden">
            <h3 class="font-medium text-gray-800 mb-2">GeoHash信息</h3>
            <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
                <div>
                    <p class="text-sm text-gray-500">当前GeoHash</p>
                    <p id="currentGeohash" class="font-medium text-gray-800">-</p>
                </div>
                <div>
                    <p class="text-sm text-gray-500">中心点坐标</p>
                    <p id="centerPoint" class="font-medium text-gray-800">-</p>
                </div>
                <div>
                    <p class="text-sm text-gray-500">边界范围</p>
                    <p id="boundingBox" class="font-medium text-gray-800">-</p>
                </div>
            </div>
        </div>
    </main>

    <footer class="bg-dark text-white py-4 mt-8">
        <div class="container mx-auto px-4 text-center text-sm">
            <p>© 2025 GeoHash地图可视化工具 | 基于高德地图API构建</p>
        </div>
    </footer>

    <script>
        // GeoHash算法实现
        const geohash = {
            base32: '0123456789bcdefghjkmnpqrstuvwxyz',
            
            // 编码经纬度为GeoHash
            encode: function(latitude, longitude, precision = 6) {
                let isEven = true;
                let lat = [-90.0, 90.0];
                let lon = [-180.0, 180.0];
                let bit = 0;
                let ch = 0;
                let geohash = '';
                
                while (geohash.length < precision) {
                    if (isEven) {
                        const mid = (lon[0] + lon[1]) / 2;
                        if (longitude >= mid) {
                            ch |= 1 << (4 - bit);
                            lon[0] = mid;
                        } else {
                            lon[1] = mid;
                        }
                    } else {
                        const mid = (lat[0] + lat[1]) / 2;
                        if (latitude >= mid) {
                            ch |= 1 << (4 - bit);
                            lat[0] = mid;
                        } else {
                            lat[1] = mid;
                        }
                    }
                    
                    isEven = !isEven;
                    
                    if (bit < 4) {
                        bit++;
                    } else {
                        geohash += this.base32.charAt(ch);
                        bit = 0;
                        ch = 0;
                    }
                }
                
                return geohash;
            },
            
            // 解码GeoHash为经纬度范围
            decode: function(geohash) {
                let isEven = true;
                let lat = [-90.0, 90.0];
                let lon = [-180.0, 180.0];
                
                for (let i = 0; i < geohash.length; i++) {
                    const c = geohash[i];
                    const cd = this.base32.indexOf(c);
                    
                    for (let j = 0; j < 5; j++) {
                        const mask = 1 << (4 - j);
                        if (isEven) {
                            if (cd & mask) {
                                lon[0] = (lon[0] + lon[1]) / 2;
                            } else {
                                lon[1] = (lon[0] + lon[1]) / 2;
                            }
                        } else {
                            if (cd & mask) {
                                lat[0] = (lat[0] + lat[1]) / 2;
                            } else {
                                lat[1] = (lat[0] + lat[1]) / 2;
                            }
                        }
                        isEven = !isEven;
                    }
                }
                
                const latCenter = (lat[0] + lat[1]) / 2;
                const lonCenter = (lon[0] + lon[1]) / 2;
                
                return {
                    latitude: latCenter,
                    longitude: lonCenter,
                    latitudeRange: lat,
                    longitudeRange: lon
                };
            },
            
            // 获取GeoHash的四个方向邻居
            neighbors: function(geohash) {
                const directions = [
                    [1, 0],  // 东
                    [-1, 0], // 西
                    [0, 1],  // 北
                    [0, -1]  // 南
                ];
                
                const decode = this.decode(geohash);
                const lat = decode.latitude;
                const lon = decode.longitude;
                const latRange = decode.latitudeRange[1] - decode.latitudeRange[0];
                const lonRange = decode.longitudeRange[1] - decode.longitudeRange[0];
                
                return directions.map(([dx, dy]) => {
                    const neighborLat = lat + dy * latRange * 0.99;
                    const neighborLon = lon + dx * lonRange * 0.99;
                    return this.encode(neighborLat, neighborLon, geohash.length);
                });
            },
            
            // 获取GeoHash的边界坐标点
            getBoundary: function(geohash) {
                const decode = this.decode(geohash);
                const latRange = decode.latitudeRange;
                const lonRange = decode.longitudeRange;
                
                return [
                    [lonRange[0], latRange[0]], // 西南
                    [lonRange[0], latRange[1]], // 西北
                    [lonRange[1], latRange[1]], // 东北
                    [lonRange[1], latRange[0]], // 东南
                    [lonRange[0], latRange[0]]  // 回到西南，闭合多边形
                ];
            }
        };
		
		
		// WGS84坐标系转GCJ-02坐标系（火星坐标系）
const coordinateTransform = {
    // 判断是否在中国范围内（用于决定是否需要转换）
    outOfChina: function(lng, lat) {
        return (lng < 72.004 || lng > 137.8347) || 
               (lat < 0.8293 || lat > 55.8271);
    },
    
    // 转换算法所需的辅助函数
    transformLat: function(x, y) {
        let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
        return ret;
    },
    
    transformLng: function(x, y) {
        let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
        ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
        ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
        ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
        return ret;
    },
    
    // WGS84转GCJ-02
    wgs84ToGcj02: function(wgsLng, wgsLat) {
        if (this.outOfChina(wgsLng, wgsLat)) {
            return { lng: wgsLng, lat: wgsLat };
        }
        
        const dLat = this.transformLat(wgsLng - 105.0, wgsLat - 35.0);
        const dLng = this.transformLng(wgsLng - 105.0, wgsLat - 35.0);
        const radLat = wgsLat / 180.0 * Math.PI;
        let magic = Math.sin(radLat);
        magic = 1 - 0.00669342162296594323 * magic * magic;
        const sqrtMagic = Math.sqrt(magic);
        const dLat2 = (dLat * 180.0) / ((6378245.0 * (1 - 0.00669342162296594323)) / (magic * sqrtMagic) * Math.PI);
        const dLng2 = (dLng * 180.0) / (6378245.0 / sqrtMagic * Math.cos(radLat) * Math.PI);
        
        const mgLat = wgsLat + dLat2;
        const mgLng = wgsLng + dLng2;
        
        return { lng: mgLng, lat: mgLat };
    }
};

        // 全局变量
        let map;
        let geohashPolygons = [];
        let geohashMarkers = [];
        let currentSelectedGeohash = null;

    

                // 初始化地图
        function initMap() {
            map = new AMap.Map('mapContainer', {
                zoom: 10,
                center: [116.397428, 39.90923],
                viewMode: '2D',
                lang: 'zh_cn',
                resizeEnable: true
            });
            
            // 加载地图控件插件
            AMap.plugin(['AMap.ToolBar', 'AMap.Scale', 'AMap.MapType'], function() {
                // 添加地图控件
                map.addControl(new AMap.ToolBar());
                map.addControl(new AMap.Scale());
                map.addControl(new AMap.MapType());
                
                // 初始化示例数据
                // drawGeohashes(['wx4g0s', 'wx4g0u', 'wx4g0v', 'wx4g0w']);
            });
        }

        // 绘制GeoHash网格
        // 绘制GeoHash网格
function drawGeohashes(geohashList) {
    // 清除现有图层
    clearGeohashLayers();
    
    // 过滤无效的GeoHash
    const validGeohashes = geohashList
        .map(gh => gh.trim())
        .filter(gh => gh.length > 0);
    
    if (validGeohashes.length === 0) {
        alert('请输入有效的GeoHash!');
        return;
    }
    
    // 计算中心点，用于地图定位
    let totalLat = 0;
    let totalLon = 0;
    let validCount = 0;
    
    // 绘制每个GeoHash
    validGeohashes.forEach(gh => {
        try {
            // 获取边界坐标
            const boundary = geohash.getBoundary(gh);
            
            // 转换所有坐标点到GCJ-02
            const gcjBoundary = boundary.map(coord => {
                const wgsLng = coord[1];
                const wgsLat = coord[0];
                const gcj = coordinateTransform.wgs84ToGcj02(wgsLng, wgsLat);
                return [gcj.lng, gcj.lat];
            });
            
            // 验证所有坐标点
            const isValid = gcjBoundary.every(coord => {
                return isValidCoordinate(coord[0], coord[1]);
            });
            
            if (!isValid) {
                console.error(`GeoHash ${gh} 生成的坐标超出有效范围`);
                return;
            }
            
            // 使用转换后的GCJ-02坐标
            const points = gcjBoundary;
            
            // 创建多边形
            const polygon = new AMap.Polygon({
                path: points,
                strokeColor: 'rgba(22, 93, 255, 0.7)',
                strokeWeight: 1,
                fillColor: 'rgba(22, 93, 255, 0.1)',
                zIndex: 50,
                cursor: 'pointer',
                extData: { geohash: gh }
            });
            
            // 添加事件处理... (保持原有代码不变)
            
            // 添加到地图
            polygon.setMap(map);
            geohashPolygons.push(polygon);
            
            // 添加中心点标记
            const decodeInfo = geohash.decode(gh);
            // 转换中心点坐标到GCJ-02
            const gcjCenter = coordinateTransform.wgs84ToGcj02(decodeInfo.longitude, decodeInfo.latitude);
            
            if (isValidCoordinate(gcjCenter.lng, gcjCenter.lat)) {
                const marker = new AMap.Marker({
                    position: [gcjCenter.lng, gcjCenter.lat], // 使用GCJ-02坐标
                    title: gh,
                    icon: new AMap.Icon({
                        size: new AMap.Size(18, 18),
                        image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png'
                    }),
                    zIndex: 100,
                    extData: { geohash: gh }
                });
                
                marker.setMap(map);
                geohashMarkers.push(marker);
                
                // 更新中心点计算
                totalLat += gcjCenter.lat;
                totalLon += gcjCenter.lng;
                validCount++;
            }
        } catch (error) {
            console.error(`处理GeoHash ${gh} 时出错:`, error);
            alert(`GeoHash ${gh} 格式不正确，请检查!`);
        }
    });
    
    // 计算平均中心点并调整地图视图
    if (validCount > 0) {
        const centerLat = totalLat / validCount;
        const centerLon = totalLon / validCount;
        map.setCenter([centerLon, centerLat]);
        
        // 自动调整地图级别以显示所有多边形
        setTimeout(() => {
            map.setFitView(geohashPolygons);
        }, 100);
    }
    
    // 显示第一个GeoHash的信息
    if (validGeohashes.length > 0) {
        showGeohashInfo(validGeohashes[0]);
    }
}

        // 清除GeoHash图层
        function clearGeohashLayers() {
            geohashPolygons.forEach(poly => poly.setMap(null));
            geohashMarkers.forEach(marker => marker.setMap(null));
            geohashPolygons = [];
            geohashMarkers = [];
            currentSelectedGeohash = null;
            document.getElementById('infoPanel').classList.add('hidden');
        }

        // 显示GeoHash信息
  // 显示GeoHash信息
function showGeohashInfo(gh) {
    try {
        // 更新选中状态... (保持原有代码不变)
        
        // 解码并显示信息
        const decodeInfo = geohash.decode(gh);
        const latRange = decodeInfo.latitudeRange;
        const lonRange = decodeInfo.longitudeRange;
        
        // 转换为GCJ-02坐标
        const gcjCenter = coordinateTransform.wgs84ToGcj02(decodeInfo.longitude, decodeInfo.latitude);
        const gcjSouthWest = coordinateTransform.wgs84ToGcj02(lonRange[0], latRange[0]);
        const gcjNorthEast = coordinateTransform.wgs84ToGcj02(lonRange[1], latRange[1]);
        
        document.getElementById('currentGeohash').textContent = gh;
        document.getElementById('centerPoint').textContent = 
            `WGS84: ${decodeInfo.latitude.toFixed(6)}, ${decodeInfo.longitude.toFixed(6)}\n` +
            `GCJ-02: ${gcjCenter.lat.toFixed(6)}, ${gcjCenter.lng.toFixed(6)}`;
        document.getElementById('boundingBox').textContent = 
            `WGS84 - 南: ${latRange[0].toFixed(6)}, 北: ${latRange[1].toFixed(6)}, 西: ${lonRange[0].toFixed(6)}, 东: ${lonRange[1].toFixed(6)}\n` +
            `GCJ-02 - 南: ${gcjSouthWest.lat.toFixed(6)}, 北: ${gcjNorthEast.lat.toFixed(6)}, 西: ${gcjSouthWest.lng.toFixed(6)}, 东: ${gcjNorthEast.lng.toFixed(6)}`;
        
        document.getElementById('infoPanel').classList.remove('hidden');
    } catch (error) {
        console.error('显示GeoHash信息时出错:', error);
        alert('显示GeoHash信息失败!');
    }
}
        // 页面加载完成后初始化
        document.addEventListener('DOMContentLoaded', function() {
            // 初始化地图
            initMap();
            
            // 绑定按钮事件
            document.getElementById('drawBtn').addEventListener('click', function() {
                const input = document.getElementById('geohashInput').value;
                const geohashList = input.split(/[\n,]+/);
                drawGeohashes(geohashList);
            });
            
            document.getElementById('clearBtn').addEventListener('click', function() {
                document.getElementById('geohashInput').value = '';
                clearGeohashLayers();
            });
            
            document.getElementById('helpBtn').addEventListener('click', function() {
                alert('使用说明:\n1. 在文本框中输入GeoHash（每行一个或用逗号分隔）\n2. 选择网格精度\n3. 点击"绘制"按钮显示GeoHash网格\n4. 点击网格可查看详细信息');
            });
            
            document.getElementById('aboutBtn').addEventListener('click', function() {
                alert('GeoHash地图可视化工具 v1.0\n基于高德地图API构建\n请使用WGS84坐标系');
            });
            
            // 精度选择变化事件
            document.getElementById('precisionSelect').addEventListener('change', function() {
                const input = document.getElementById('geohashInput').value;
                const geohashList = input.split(/[\n,]+/).filter(gh => gh.trim().length > 0);
                
                if (geohashList.length > 0) {
                    const precision = parseInt(this.value);
                    const newGeohashes = geohashList.map(gh => {
                        const decoded = geohash.decode(gh.trim());
                        return geohash.encode(decoded.latitude, decoded.longitude, precision);
                    });
                    
                    document.getElementById('geohashInput').value = newGeohashes.join('\n');
                    drawGeohashes(newGeohashes);
                }
            });
        });
    </script>
</body>
</html>
    